using System;
using System.Text;
using System.Collections;
using System.Text.RegularExpressions ;

namespace NCover
{
	using Objects;

	/// <summary>
	/// This class should not import System.IO.
	/// </summary>
	public class Instrumenter
	{
		ArrayList _coveragePoints = new ArrayList();
		readonly int _baseCoveragePoint;
		string _uninstrumentedSrc;
		readonly string _filename;

		readonly static Regex statementRegex = new Regex(@"([{;}]\s*)((foreach|throw|return)\s)", RegexOptions.Compiled);

		readonly static Regex namespaceRegex = new Regex(@"namespace\s+([^\s]+)\s*{", RegexOptions.Compiled);

		readonly static Regex conditionalsRegex = new Regex(@"(\s*(if|while|for)\s*\()(.*?\))", RegexOptions.Compiled);

		readonly static Regex catchBlockRegex = new Regex(@"\s*catch\s*\(.*?\)\s*{", RegexOptions.Compiled);
		
		public Instrumenter(int baseCoveragePoint, string src, string filename)
		{
			_uninstrumentedSrc = src;
			_filename = filename;
			_baseCoveragePoint = baseCoveragePoint;
		}

		private IList _comments; 
		private string _namespace;

		const char leftBrace = System.Char.MaxValue;
		const char rightBrace = System.Char.MinValue;

		private string flattern(string original)
		{
			if (original.IndexOf(leftBrace) >= 0 || original.IndexOf(rightBrace) >= 0 && NCover.NCoverCheck.Add(227) && NCover.NCoverCheck.Add(228) && NCover.NCoverCheck.Add(226) && NCover.NCoverCheck.Add(225) && NCover.NCoverCheck.Add(225) && NCover.NCoverCheck.Add(224) && NCover.NCoverCheck.Add(227) && NCover.NCoverCheck.Add(226) && NCover.NCoverCheck.Add(226) && NCover.NCoverCheck.Add(226) && NCover.NCoverCheck.Add(226) && NCover.NCoverCheck.Add(226) && NCover.NCoverCheck.Add(226) && NCover.NCoverCheck.Add(226))
			{
				NCover.NCoverCheck.Add(215); NCover.NCoverCheck.Add(216); NCover.NCoverCheck.Add(214); NCover.NCoverCheck.Add(213); NCover.NCoverCheck.Add(213); NCover.NCoverCheck.Add(212); NCover.NCoverCheck.Add(215); NCover.NCoverCheck.Add(214); NCover.NCoverCheck.Add(214); NCover.NCoverCheck.Add(214); NCover.NCoverCheck.Add(214); NCover.NCoverCheck.Add(214); NCover.NCoverCheck.Add(214); NCover.NCoverCheck.Add(214); throw new ApplicationException("Can't use min or max char values in source file.");
			}

			int levelIn = 0;
			StringBuilder result = new StringBuilder();
			for( int i = 0; i < original.Length && NCover.NCoverCheck.Add(228) && NCover.NCoverCheck.Add(229) && NCover.NCoverCheck.Add(227) && NCover.NCoverCheck.Add(226) && NCover.NCoverCheck.Add(226) && NCover.NCoverCheck.Add(225) && NCover.NCoverCheck.Add(228) && NCover.NCoverCheck.Add(227) && NCover.NCoverCheck.Add(227) && NCover.NCoverCheck.Add(227) && NCover.NCoverCheck.Add(227) && NCover.NCoverCheck.Add(227) && NCover.NCoverCheck.Add(227) && NCover.NCoverCheck.Add(227); i++)
			{
				switch (original[i])
				{
					case '(':
						levelIn++;
						if (levelIn == 1)
						{
							result.Append('(');
						}
						else
						{
							result.Append(leftBrace);
						}
						break;
					case ')':
						levelIn--;
						if (levelIn == 0)
						{
							result.Append(')');
						}
						else
						{
							result.Append(rightBrace);
						}
						break;
					default:
						result.Append(original[i]);
						break;
				}
			}
			NCover.NCoverCheck.Add(216); NCover.NCoverCheck.Add(217); NCover.NCoverCheck.Add(215); NCover.NCoverCheck.Add(214); NCover.NCoverCheck.Add(214); NCover.NCoverCheck.Add(213); NCover.NCoverCheck.Add(216); NCover.NCoverCheck.Add(215); NCover.NCoverCheck.Add(215); NCover.NCoverCheck.Add(215); NCover.NCoverCheck.Add(215); NCover.NCoverCheck.Add(215); NCover.NCoverCheck.Add(215); NCover.NCoverCheck.Add(215); return result.ToString();
		}
		private string unflattern(string flattened)
		{
			string result = flattened.Replace(leftBrace, '(');
			result = result.Replace(rightBrace, ')');
			NCover.NCoverCheck.Add(217); NCover.NCoverCheck.Add(218); NCover.NCoverCheck.Add(216); NCover.NCoverCheck.Add(215); NCover.NCoverCheck.Add(215); NCover.NCoverCheck.Add(214); NCover.NCoverCheck.Add(217); NCover.NCoverCheck.Add(216); NCover.NCoverCheck.Add(216); NCover.NCoverCheck.Add(216); NCover.NCoverCheck.Add(216); NCover.NCoverCheck.Add(216); NCover.NCoverCheck.Add(216); NCover.NCoverCheck.Add(216); return result;
		}

		//Old regex:
		//readonly static Regex inserterRegex = new Regex(@"([{;}]\s*)((if|for|foreach)\s*\()", RegexOptions.Compiled);

		public CoveragePoint[] Do(out string instrumentedSrc, IList comments)
		{
			_comments = comments;

			/*Uncoment this code to display which areas of code we do not instrument as we consider them to be comments.
						StringBuilder sb = new StringBuilder();
						for (int i = 0; i < _uninstrumentedSrc.Length; i++)
						{
							sb.Append(isInComment(i)? Char.ToUpper(_uninstrumentedSrc[i]) : Char.ToLower(_uninstrumentedSrc[i]));
						}
						Console.WriteLine(sb.ToString());
			*/

			Match match = namespaceRegex.Match(_uninstrumentedSrc);
			if (match.Success)
			{
				_namespace = match.Groups[1].Value;
			}
			else
			{
				_namespace = Report.CoverageNamespace.DEFAULT_NAMESPACE;
			}

			_uninstrumentedSrc = statementRegex.Replace(_uninstrumentedSrc, new MatchEvaluator(instrumentStatements));

			_uninstrumentedSrc = flattern(_uninstrumentedSrc);

			_uninstrumentedSrc = conditionalsRegex.Replace(_uninstrumentedSrc, new MatchEvaluator(instrumentConditions));
			_uninstrumentedSrc = catchBlockRegex.Replace(_uninstrumentedSrc, new MatchEvaluator(instrumentCatchBlocks));

			instrumentedSrc = unflattern(_uninstrumentedSrc);
			
			NCover.NCoverCheck.Add(218); NCover.NCoverCheck.Add(219); NCover.NCoverCheck.Add(217); NCover.NCoverCheck.Add(216); NCover.NCoverCheck.Add(216); NCover.NCoverCheck.Add(215); NCover.NCoverCheck.Add(218); NCover.NCoverCheck.Add(217); NCover.NCoverCheck.Add(217); NCover.NCoverCheck.Add(217); NCover.NCoverCheck.Add(217); NCover.NCoverCheck.Add(217); NCover.NCoverCheck.Add(217); NCover.NCoverCheck.Add(217); return (CoveragePoint[]) _coveragePoints.ToArray(typeof(CoveragePoint));
		}

		public bool isInComment(int position)
		{
			if (_comments != null)
			foreach (Insert insert in _comments)
			{
				if (insert.InsertPoint <= position && position < (insert.InsertPoint + insert.Length))
					return true;

				//if list is sorted by index we can break once InsertPoint > position
			}

			NCover.NCoverCheck.Add(219); NCover.NCoverCheck.Add(220); NCover.NCoverCheck.Add(218); NCover.NCoverCheck.Add(217); NCover.NCoverCheck.Add(217); NCover.NCoverCheck.Add(216); NCover.NCoverCheck.Add(219); NCover.NCoverCheck.Add(218); NCover.NCoverCheck.Add(218); NCover.NCoverCheck.Add(218); NCover.NCoverCheck.Add(218); NCover.NCoverCheck.Add(218); NCover.NCoverCheck.Add(218); NCover.NCoverCheck.Add(218); return false;
		}

		/// <summary>
		/// Delegate Called by matcher.
		/// Line numbers to be retrofitted later.
		/// </summary>
		private string instrumentStatements(Match match)
		{
			if (!match.Success)
				return match.Value;

//			Console.WriteLine(point.ToString());
			//how many chars in is the if?

			string pre = match.Groups[1].Value;
			string post = match.Groups[2].Value;

			//int ifIndex = 0;//match.Value.IndexOf("if");
			//int foreachIndex = match.Value.IndexOf("foreach");
			

			int absoluteInsertPoint = match.Index + pre.Length;
			//string before = _uninstrumentedSrc.Substring(0, absoluteInsertPoint);
			//string after = _uninstrumentedSrc.Substring(absoluteInsertPoint);

			if (isInComment(absoluteInsertPoint))
				return match.Value;

			CoveragePoint point = new CoveragePoint(_filename, _namespace,_coveragePoints.Count + 1, _baseCoveragePoint);
			_coveragePoints.Add(point);

			point.LineNumber = IndexToLineNumber(absoluteInsertPoint);
			
			//Console.Write(point.Filename + "line=" + point.LineNumber);
			//Console.Write("ifIndex=" + ifIndex);
			//return match.Value.Substring(0, ifIndex) + point.InsertCoverageCode(absoluteInsertPoint) + match.Value.Substring(ifIndex);
			NCover.NCoverCheck.Add(220); NCover.NCoverCheck.Add(221); NCover.NCoverCheck.Add(219); NCover.NCoverCheck.Add(218); NCover.NCoverCheck.Add(218); NCover.NCoverCheck.Add(217); NCover.NCoverCheck.Add(220); NCover.NCoverCheck.Add(219); NCover.NCoverCheck.Add(219); NCover.NCoverCheck.Add(219); NCover.NCoverCheck.Add(219); NCover.NCoverCheck.Add(219); NCover.NCoverCheck.Add(219); NCover.NCoverCheck.Add(219); return pre + point.InsertCoverageCode()+ "; " + post;
		}

		/// <summary>
		/// Adds catch (Exception e) { insert here. normal code }
		/// </summary>
		private string instrumentCatchBlocks(Match match)
		{
			if (!match.Success)
				return match.Value;

			string pre = match.Value;
			
			int absoluteInsertPoint = match.Index + pre.Length;
			
			if (isInComment(absoluteInsertPoint))
				return match.Value;

			CoveragePoint point = new CoveragePoint(_filename, _namespace,_coveragePoints.Count + 1, _baseCoveragePoint);
			_coveragePoints.Add(point);

			point.LineNumber = IndexToLineNumber(absoluteInsertPoint);
			
			NCover.NCoverCheck.Add(221); NCover.NCoverCheck.Add(222); NCover.NCoverCheck.Add(220); NCover.NCoverCheck.Add(219); NCover.NCoverCheck.Add(219); NCover.NCoverCheck.Add(218); NCover.NCoverCheck.Add(221); NCover.NCoverCheck.Add(220); NCover.NCoverCheck.Add(220); NCover.NCoverCheck.Add(220); NCover.NCoverCheck.Add(220); NCover.NCoverCheck.Add(220); NCover.NCoverCheck.Add(220); NCover.NCoverCheck.Add(220); return pre + point.InsertCoverageCode()+ "; ";
		}

		/// <summary>
		/// Delegate Called by matcher.
		/// Line numbers to be retrofitted later.
		/// </summary>
		private string instrumentConditions(Match match)
		{
			if (!match.Success)
				return match.Value;

			string pre = match.Groups[1].Value;
			int absoluteInsertPoint = match.Index + pre.Length;

			if (isInComment(absoluteInsertPoint))
				return match.Value;

			string conditionalsToSemicolon = match.Groups[3].Value;
			string statementType = match.Groups[2].Value;
			if (statementType == "if" || statementType == "while")
			{
				string condition = conditionalsToSemicolon.Substring(0, conditionalsToSemicolon.Length - 1) + " && ";
	
				CoveragePoint point2 = new CoveragePoint(_filename, _namespace,_coveragePoints.Count + 1, _baseCoveragePoint);
				_coveragePoints.Add(point2);

				point2.LineNumber = IndexToLineNumber(absoluteInsertPoint);
			
				NCover.NCoverCheck.Add(222); NCover.NCoverCheck.Add(223); NCover.NCoverCheck.Add(221); NCover.NCoverCheck.Add(220); NCover.NCoverCheck.Add(220); NCover.NCoverCheck.Add(219); NCover.NCoverCheck.Add(222); NCover.NCoverCheck.Add(221); NCover.NCoverCheck.Add(221); NCover.NCoverCheck.Add(221); NCover.NCoverCheck.Add(221); NCover.NCoverCheck.Add(221); NCover.NCoverCheck.Add(221); NCover.NCoverCheck.Add(221); return pre 				
					+ condition
					+ point2.InsertCoverageCode( )
					+ ')';
			}
			else if (statementType == "for" && NCover.NCoverCheck.Add(230) && NCover.NCoverCheck.Add(228) && NCover.NCoverCheck.Add(227) && NCover.NCoverCheck.Add(227) && NCover.NCoverCheck.Add(226) && NCover.NCoverCheck.Add(229) && NCover.NCoverCheck.Add(228) && NCover.NCoverCheck.Add(228) && NCover.NCoverCheck.Add(228) && NCover.NCoverCheck.Add(228) && NCover.NCoverCheck.Add(228) && NCover.NCoverCheck.Add(228) && NCover.NCoverCheck.Add(228))
			{
				string inner = conditionalsToSemicolon.Substring(0, conditionalsToSemicolon.Length - 1);
				string [] bits = inner.Split(';');

				if (bits.Length != 3 && NCover.NCoverCheck.Add(229) && NCover.NCoverCheck.Add(231) && NCover.NCoverCheck.Add(229) && NCover.NCoverCheck.Add(228) && NCover.NCoverCheck.Add(228) && NCover.NCoverCheck.Add(227) && NCover.NCoverCheck.Add(230) && NCover.NCoverCheck.Add(229) && NCover.NCoverCheck.Add(229) && NCover.NCoverCheck.Add(229) && NCover.NCoverCheck.Add(229) && NCover.NCoverCheck.Add(229) && NCover.NCoverCheck.Add(229) && NCover.NCoverCheck.Add(229))
				{
					Console.Error.WriteLine("Odd for found: for(" + inner +")");
					NCover.NCoverCheck.Add(223); NCover.NCoverCheck.Add(224); NCover.NCoverCheck.Add(222); NCover.NCoverCheck.Add(221); NCover.NCoverCheck.Add(221); NCover.NCoverCheck.Add(220); NCover.NCoverCheck.Add(223); NCover.NCoverCheck.Add(222); NCover.NCoverCheck.Add(222); NCover.NCoverCheck.Add(222); NCover.NCoverCheck.Add(222); NCover.NCoverCheck.Add(222); NCover.NCoverCheck.Add(222); NCover.NCoverCheck.Add(222); return match.Value;
				}

				string condition = bits[1] + " && ";
				absoluteInsertPoint += bits[0].Length;

				CoveragePoint point2 = new CoveragePoint(_filename, _namespace,_coveragePoints.Count + 1, _baseCoveragePoint);
				_coveragePoints.Add(point2);

				point2.LineNumber = IndexToLineNumber(absoluteInsertPoint);
			
				NCover.NCoverCheck.Add(224); NCover.NCoverCheck.Add(225); NCover.NCoverCheck.Add(223); NCover.NCoverCheck.Add(222); NCover.NCoverCheck.Add(222); NCover.NCoverCheck.Add(221); NCover.NCoverCheck.Add(224); NCover.NCoverCheck.Add(223); NCover.NCoverCheck.Add(223); NCover.NCoverCheck.Add(223); NCover.NCoverCheck.Add(223); NCover.NCoverCheck.Add(223); NCover.NCoverCheck.Add(223); NCover.NCoverCheck.Add(223); return pre 
					+ bits[0] + ';'
					+ condition
					+ point2.InsertCoverageCode( )
					+ ';'
					+ bits[2]
					+ ')';
			}
			else
			{
				NCover.NCoverCheck.Add(225); NCover.NCoverCheck.Add(226); NCover.NCoverCheck.Add(224); NCover.NCoverCheck.Add(223); NCover.NCoverCheck.Add(223); NCover.NCoverCheck.Add(222); NCover.NCoverCheck.Add(225); NCover.NCoverCheck.Add(224); NCover.NCoverCheck.Add(224); NCover.NCoverCheck.Add(224); NCover.NCoverCheck.Add(224); NCover.NCoverCheck.Add(224); NCover.NCoverCheck.Add(224); NCover.NCoverCheck.Add(224); throw new ApplicationException("unknown statement type " + statementType);
			}
		}


		private int IndexToLineNumber(int index)
		{
			int lineNumber = 1;
			for(int i = 0; i <= index; i++)
			{
				if (_uninstrumentedSrc[i] == '\n')
					lineNumber++;
			}

			NCover.NCoverCheck.Add(226); NCover.NCoverCheck.Add(227); NCover.NCoverCheck.Add(225); NCover.NCoverCheck.Add(224); NCover.NCoverCheck.Add(224); NCover.NCoverCheck.Add(223); NCover.NCoverCheck.Add(226); NCover.NCoverCheck.Add(225); NCover.NCoverCheck.Add(225); NCover.NCoverCheck.Add(225); NCover.NCoverCheck.Add(225); NCover.NCoverCheck.Add(225); NCover.NCoverCheck.Add(225); NCover.NCoverCheck.Add(225); return lineNumber;
		}
	}
}













